﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint;
using System.Globalization;

namespace SPVSX.Explorer.Commands.SharePoint
{
    /// <summary>
    /// The ElementProvider provides access to Elements within the installed Features
    /// </summary>
    public class ElementProvider
    {
        #region Methods

        #region General

        /// <summary>
        /// Get a new <see cref="ElementProvider"/> instance
        /// </summary>
        /// <returns></returns>
        public static ElementProvider GetAvailableProvider()
        {
            return new ElementProvider();

        }

        #endregion

        #region Custom Action Groups

        /// <summary>
        /// Query the features for Custom Action Groups by their location attribute
        /// </summary>
        /// <param name="web">The <see cref="SPWeb"/> to use</param>
        /// <param name="scope">The scope</param>
        /// <param name="location">The location attribute to match</param>
        /// <returns>A list of <see cref="CustomActionGroupElement"/> matching the query</returns>
        public List<CustomActionGroupElement> QueryForCustomActionGroupsByLocation(SPWeb web, string scope, string location)
        {
            List<SPElementDefinition> features = GetFeaturesContainingElementType(web, scope, CustomActionGroupElement.RootTag);

            Dictionary<string, string> dictAttrPattern = new Dictionary<string, string>();
            dictAttrPattern.Add("Location", location);

            List<SPElementDefinition> subsetOfFeatures = QueryForElements<SPElementDefinition>(dictAttrPattern, features, new CultureInfo((int)web.Language));

            List<CustomActionGroupElement> customActionGroups = new List<CustomActionGroupElement>();
            foreach (SPElementDefinition elementDefinition in subsetOfFeatures)
            {
                customActionGroups.Add(new CustomActionGroupElement(elementDefinition));
            }

            customActionGroups.Sort();
            return customActionGroups;
        }

        /// <summary>
        /// Query the features for Custom Action Groups by their location and group id attributes
        /// </summary>
        /// <param name="web">The <see cref="SPWeb"/> to use</param>
        /// <param name="scope">The scope</param>
        /// <param name="location">The location attribute to match</param>
        /// <param name="groupId">The group id</param>
        /// <returns>A list of <see cref="CustomActionGroupElement"/> matching the query</returns>
        public List<CustomActionGroupElement> QueryForCustomActionGroupsByLocationAndGroupId(SPWeb web, string scope, string location, string groupId)
        {
            List<SPElementDefinition> features = GetFeaturesContainingElementType(web, scope, CustomActionGroupElement.RootTag);

            Dictionary<string, string> dictAttrPattern = new Dictionary<string, string>();
            dictAttrPattern.Add("Location", location);

            List<SPElementDefinition> subsetOfFeatures = QueryForElements<SPElementDefinition>(dictAttrPattern, features, new CultureInfo((int)web.Language));

            List<CustomActionGroupElement> customActionGroups = new List<CustomActionGroupElement>();
            foreach (SPElementDefinition elementDefinition in subsetOfFeatures)
            {
                CustomActionGroupElement groupElement = new CustomActionGroupElement(elementDefinition);
                if (groupElement.Id == groupId)
                {
                    customActionGroups.Add(groupElement);
                }
            }

            customActionGroups.Sort();
            return customActionGroups;
        }

        #endregion

        #region Custom Actions

        /// <summary>
        /// Query the features for Custom Actions
        /// </summary>
        /// <param name="web">The <see cref="SPWeb"/> to use</param>
        /// <param name="scope">The scope</param>
        /// <param name="location">The location attribute to match</param>
        /// <param name="groupId">The group id</param>
        /// <returns>A list of <see cref="CustomActionElement"/> matching the query</returns>
        protected List<CustomActionElement> QueryForCustomActions(SPWeb web, string scope, string location, string groupId)
        {
            List<SPElementDefinition> features = GetFeaturesContainingElementType(web, scope, CustomActionElement.RootTag);

            Dictionary<string, string> dictAttrPattern = new Dictionary<string, string>();
            dictAttrPattern.Add("Location", location);
            dictAttrPattern.Add("GroupId", groupId);

            List<SPElementDefinition> subsetOfFeatures = QueryForElements<SPElementDefinition>(dictAttrPattern, features, new CultureInfo((int)web.Language));

            List<CustomActionElement> customActionElements = new List<CustomActionElement>();
            foreach (SPElementDefinition elementDefinition in subsetOfFeatures)
            {
                customActionElements.Add(new CustomActionElement(elementDefinition));
            }

            customActionElements.Sort();
            return customActionElements;
        }

        /// <summary>
        /// Query the features for Hide Custom Actions
        /// </summary>
        /// <param name="web">The <see cref="SPWeb"/> to use</param>
        /// <param name="scope">The scope</param>
        /// <param name="location">The location attribute to match</param>
        /// <param name="groupId">The group id</param>
        /// <returns>A list of <see cref="CustomActionElement"/> matching the query</returns>
        protected List<HideCustomActionElement> QueryForHideCustomActions(SPWeb web, string scope, string location, string groupId)
        {
            List<SPElementDefinition> features = GetFeaturesContainingElementType(web, scope, HideCustomActionElement.RootTag);

            Dictionary<string, string> dictAttrPattern = new Dictionary<string, string>();
            dictAttrPattern.Add("Location", location);
            dictAttrPattern.Add("GroupId", groupId);

            List<SPElementDefinition> subsetOfFeatures = QueryForElements<SPElementDefinition>(dictAttrPattern, features, new CultureInfo((int)web.Language));

            List<HideCustomActionElement> customHideCustomActions = new List<HideCustomActionElement>();
            foreach (SPElementDefinition elementDefinition in subsetOfFeatures)
            {
                customHideCustomActions.Add(new HideCustomActionElement(elementDefinition));
            }

            return customHideCustomActions;
        }

        /// <summary>
        /// Query the features for security trimmed Custom Actions
        /// </summary>
        /// <param name="web">The <see cref="SPWeb"/> to use</param>
        /// <param name="scope">The scope</param>
        /// <param name="location">The location attribute to match</param>
        /// <param name="groupId">The group id</param>
        /// <returns>A list of <see cref="CustomActionElement"/> matching the query</returns>
        public List<CustomActionElement> QueryForSecurityTrimmedCustomActions(SPWeb web, string scope, string location, string groupId)
        {
            List<SPElementDefinition> features = GetFeaturesContainingElementType(web, scope, CustomActionElement.RootTag);

            Dictionary<string, string> dictAttrPattern = new Dictionary<string, string>();
            dictAttrPattern.Add("Location", location);
            dictAttrPattern.Add("GroupId", groupId);

            //Get the CustomAction's
            List<SPElementDefinition> subsetOfFeatures = QueryForElements<SPElementDefinition>(dictAttrPattern, features, new CultureInfo((int)web.Language));

            //Get the HideCustomAction's
            List<HideCustomActionElement> customHideActionElements = QueryForHideCustomActions(web,
                                                                           scope,
                                                                           location,
                                                                           groupId);

            List<CustomActionElement> customActionElements = new List<CustomActionElement>();
            foreach (SPElementDefinition elementDefinition in subsetOfFeatures)
            {
                CustomActionElement action = new CustomActionElement(elementDefinition);

                if (customHideActionElements.Count > 0)
                {
                    bool match = customHideActionElements.Exists(delegate(HideCustomActionElement current)
                    {
                        return (current.HideActionId == action.Id);
                    });
                    if (match)
                    {
                        continue;
                    }
                }

                if (action.RequireSiteAdministrator)
                {
                    if (action.RequireSiteAdministrator == SPContext.Current.Web.CurrentUser.IsSiteAdmin)
                    {
                        customActionElements.Add(action);
                    }
                }
                else
                {
                    // allow the item to be added if they have permissions or no
                    // permission mask was specified
                    if (action.Rights == SPBasePermissions.EmptyMask)/// || action.UserHasRights())
                    {
                        customActionElements.Add(action);
                    }
                }
            }

            customActionElements.Sort();
            return customActionElements;
        }

        /// <summary>
        /// Query the features for all Custom Actions
        /// </summary>
        /// <param name="web">The <see cref="SPWeb"/> to use</param>
        /// <param name="scope">The scope</param>
        /// <returns>A list of <see cref="CustomActionElement"/> matching the query</returns>
        public List<CustomActionElement> QueryForAllCustomActions(SPWeb web, string scope)
        {
            List<SPElementDefinition> features = GetFeaturesContainingElementType(web, scope, CustomActionElement.RootTag);

            //Get the CustomAction's
            List<SPElementDefinition> subsetOfFeatures = QueryForElements<SPElementDefinition>(null, features, new CultureInfo((int)web.Language));

            List<CustomActionElement> customActionElements = new List<CustomActionElement>();
            foreach (SPElementDefinition elementDefinition in subsetOfFeatures)
            {
                CustomActionElement action = new CustomActionElement(elementDefinition);

                customActionElements.Add(action);
            }

            customActionElements.Sort();
            return customActionElements;
        }

        #endregion

        #region Internal Helpers

        /// <summary>
        /// Get features containing the specified element type
        /// </summary>
        /// <param name="web">The <see cref="SPWeb"/></param>
        /// <param name="scope">The scope to look at</param>
        /// <param name="typeOfInterest">The type of interest</param>
        /// <returns>The <see cref="SPElementDefinition"/>s of interest</returns>
        internal List<SPElementDefinition> GetFeaturesContainingElementType(SPWeb web, string scope, string typeOfInterest)
        {
            List<SPElementDefinition> features = new List<SPElementDefinition>();
            if (string.IsNullOrEmpty(scope))
            {
                return GetAnyScopedFeaturesContainingType(web, typeOfInterest, features);
            }

            try
            {
                SPFeatureScope featureScope = (SPFeatureScope)Enum.Parse(typeof(SPFeatureScope), scope);
                switch (featureScope)
                {
                    case SPFeatureScope.Farm:
                        Utility.GetFeaturesWhichContainElementType(new CultureInfo((int)web.Language), SPWebService.ContentService.Features, typeOfInterest, ref features);
                        return features;
                    case SPFeatureScope.WebApplication:
                        Utility.GetFeaturesWhichContainElementType(new CultureInfo((int)web.Language), web.Site.WebApplication.Features, typeOfInterest, ref features);
                        return features;
                    case SPFeatureScope.Site:
                        Utility.GetFeaturesWhichContainElementType(new CultureInfo((int)web.Language), web.Site.Features, typeOfInterest, ref features);
                        return features;
                    case SPFeatureScope.Web:
                        Utility.GetFeaturesWhichContainElementType(new CultureInfo((int)web.Language), web.Features, typeOfInterest, ref features);
                        return features;
                }
            }
            catch (ArgumentNullException)
            {
                return GetAnyScopedFeaturesContainingType(web, typeOfInterest, features);
            }
            catch (ArgumentException)
            {
                return GetAnyScopedFeaturesContainingType(web, typeOfInterest, features);
            }

            return features;
        }

        /// <summary>
        /// Get any scoped features containing the type
        /// </summary>
        /// <param name="web">The <see cref="SPWeb"/></param>
        /// <param name="typeOfInterest">The type of element to locate</param>
        /// <param name="features">The features to query</param>
        /// <returns>Matching features</returns>
        protected List<SPElementDefinition> GetAnyScopedFeaturesContainingType(SPWeb web, string typeOfInterest, List<SPElementDefinition> features)
        {
            Utility.GetFeaturesWhichContainElementType(new CultureInfo((int)web.Language), web.Features, typeOfInterest, ref features);
            Utility.GetFeaturesWhichContainElementType(new CultureInfo((int)web.Language), web.Site.Features, typeOfInterest, ref features);
            Utility.GetFeaturesWhichContainElementType(new CultureInfo((int)web.Language), web.Site.WebApplication.Features, typeOfInterest, ref features);
            Utility.GetFeaturesWhichContainElementType(new CultureInfo((int)web.Language), SPWebService.ContentService.Features, typeOfInterest, ref features);
            return features;
        }

        /// <summary>
        /// Query for elements
        /// </summary>
        /// <typeparam name="TElementType">The type</typeparam>
        /// <param name="dictAttrPattern">The patterns</param>
        /// <param name="features">The features to query</param>
        /// <param name="ciElements">The elements</param>
        /// <returns>Matching features</returns>
        internal List<TElementType> QueryForElements<TElementType>(Dictionary<string, string> dictAttrPattern,
            List<SPElementDefinition> features, CultureInfo ciElements) where TElementType : SPElementDefinition
        {
            List<Dictionary<string, string>> lstdictAttrPatterns = null;
            if (dictAttrPattern != null)
            {
                lstdictAttrPatterns = new List<Dictionary<string, string>>();
                lstdictAttrPatterns.Add(dictAttrPattern);
                return QueryForElementsJoinOR<TElementType>(dictAttrPattern, features, ciElements);
            }
            else
            {
                return QueryForElementsJoinOR<TElementType>(null, features, ciElements);
            }
        }

        /// <summary>
        /// Query for elements
        /// </summary>
        /// <typeparam name="TElementType">The type</typeparam>
        /// <param name="lstdictAttrPatterns">The patterns</param>
        /// <param name="features">The features to query</param>
        /// <param name="culture">The culture</param>
        /// <returns>The elements</returns>
        internal List<TElementType> QueryForElementsJoinOR<TElementType>(Dictionary<string, string> lstdictAttrPatterns,
            List<SPElementDefinition> features, CultureInfo culture) where TElementType : SPElementDefinition
        {
            List<TElementType> list = new List<TElementType>();

            if (lstdictAttrPatterns != null)
            {
                foreach (SPElementDefinition elementDefinition in features)
                {
                    if (PatternMatchElementWithAttributes(elementDefinition, lstdictAttrPatterns))
                    {
                        TElementType local = elementDefinition as TElementType;
                        list.Add(local);
                    }
                }
            }
            else
            {
                foreach (SPElementDefinition elementDefinition in features)
                {
                    TElementType local = elementDefinition as TElementType;
                    list.Add(local);
                }
            }
            return list;
        }

        /// <summary>
        /// Pattern match elements with the supplied elements
        /// </summary>
        /// <param name="elememtDefinition">The <see cref="SPElementDefinition"/> being compared</param>
        /// <param name="dictAttrPattern">The Dictionary containing the patterns</param>
        /// <returns>True if a match is found</returns>
        internal bool PatternMatchElementWithAttributes(SPElementDefinition elememtDefinition, Dictionary<string, string> dictAttrPattern)
        {
            XmlAttributeCollection attributes = elememtDefinition.XmlDefinition.Attributes;

            foreach (KeyValuePair<string, string> keyValuePair in dictAttrPattern)
            {
                XmlAttribute attribute = attributes[keyValuePair.Key];

                //TODO: need a clever bit here to check all dict values ae true

                if (attribute != null)
                {
                    //Absolute casing sensitive comparison as the data comes from the Feature.xml's
                    if (attribute.Value != keyValuePair.Value)
                    {
                        return false;
                    }
                }
                else
                {
                    //If the attribute is null just return
                    return false;
                }
            }
            return true;
        }

        #endregion

        #endregion
    }
}
